﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: HypergeometricDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Maths.Functions;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a hypergeometric distribution with probability parameter N, m and n.
    /// <see>Mathematica: ref/HypergeometricDistribution</see>
    /// <see>http://en.wikipedia.org/wiki/Hypergeometric_distribution</see>
    /// </summary>
    public sealed class HypergeometricDistribution : IUnivariateDistribution<int>, IEquatable<HypergeometricDistribution>
    {
        #region Private variables

        private int _p; // the population size
        private int _m; // the number of successes in the population 
        private int _n; // the size of the sample

        #endregion

        #region Public properties

        /// <summary>Gets or sets the population size of a hypergeometric distribution.</summary>
        public int P
        {
            get { return _p; }
            set
            {
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value", "p is expected to be a positive integer.");
                _p = value;
            }
        }

        /// <summary>Gets or sets the number of successes in the population of a hypergeometric distribution.</summary>
        public int M
        {
            get { return _m; }
            set
            {
                if (value > _p || value < 0)
                    throw new ArgumentOutOfRangeException("value", "m must be in interval [0, p]."); _m = value;
            }
        }

        /// <summary>Gets or sets the size of the sample of a hypergeometric distribution.</summary>
        public int N
        {
            get { return _n; }
            set
            {
                if (value > _p || value <= 0)
                    throw new ArgumentOutOfRangeException("value", "n must be in interval [0, p]."); _n = value;
            }
        }

        /// <summary>Gets the range of a hypergeometric distribution.</summary>
        public IRange<int> Domain { get { return new Range<int>(Math.Max(0, _n + _m + _p), Math.Min(_m, _n)); } }

        /// <summary>Gets the mean of a hypergeometric distribution.</summary>
        public double Mean { get { return _n * _m / (double)_p; } }

        /// <summary>Gets the median of a hypergeometric distribution.</summary>
        public double Median { get { throw new Exception("Median is undefined for hypergeometric distribution."); } }

        /// <summary>Gets the mode of a hypergeometric distribution.</summary>
        public double Mode { get { return (_n + 1) * (_m + 1) / (double)(_p + 2); } }

        /// <summary>Gets the variance of a hypergeometric distribution.</summary>
        public double Variance { get { return _n * (_m / (double)_p) * (1 - _m / (double)_p) * (_p - _n) / (_p - 1); } }

        /// <summary>Gets the skewness of a hypergeometric distribution.</summary>
        public double Skewness { get { return (_p - 2 * _m) * Math.Sqrt(_p - 1) * (_p - 2 * _n) / (Math.Sqrt(_n * _m * (_p - _m) * (_p - _n)) * (_p - 2)); } }

        /// <summary>Gets the kurtosis of a hypergeometric distribution.</summary>
        public double Kurtosis { get { return (_p * _p * (_p - 1) / (double)(_n * (_p - 2) * (_p - 3) * (_p - _n))) * (_p * (_p + 1) - 6 * _p * (_p - _n) / (double)(_m * (_p - _m)) + 3 * _n * (_p - _n) * (_p + 6) / (double)(_p * _p) - 6); } }

        /// <summary>Gets the entropy of a hypergeometric distribution.</summary>
        public double Entropy { get { throw new Exception("Entropy is undefined for hypergeometric distribution."); } }

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiates a hypergeometric distribution.
        /// </summary>
        /// <param name="p">Population size</param>
        /// <param name="m">Number of successes</param>
        /// <param name="n">Size of the sample</param>
        public HypergeometricDistribution(int p, int m, int n)
        {
            if (p <= 0)
                throw new ArgumentOutOfRangeException("p", "p is expected to be a positive integer.");
            if (m > p || m < 0)
                throw new ArgumentOutOfRangeException("m", "m must be in interval [0, p].");
            if (n > p || n <= 0)
                throw new ArgumentOutOfRangeException("n", "n must be in interval [0, p].");

            _p = p;
            _m = m;
            _n = n;
        }

        #endregion

        #region ICloneable implementation

        public HypergeometricDistribution Clone()
        {
            return new HypergeometricDistribution(_p, _m, _n);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Returns the hypergeometric density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>hypergeometric density</returns>
        public double Pdf(int value)
        {
            var a = _n + _m - _p;
            var min = 0 > a ? 0 : a;
            var max = _m > _n ? _n : _m;
            if (value < min || value > max)
                throw new ArgumentOutOfRangeException("value", "value must be in interval [max(0, n + m - p), min(m, n)].");

            return CountingFunctions.Hypergeometric(_p, _m, _n, value);
        }

        /// <summary>
        /// Returns the cummulative hypergeometric density given value
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative hypergeometric density</returns>
        public double Cdf(int value)
        {
            return Enumerable.Range(0, value + 1).Sum(i => Pdf(i));
        }

        /// <summary>
        /// Returns the value of the hypergeometric distribution for given probability.
        /// </summary>
        /// <param name="p">probability</param>
        /// <returns></returns>
        public int Quantile(double p)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns a random value from the hypergeometric distribution.
        /// </summary>
        /// <returns>random value</returns>
        public int Sample(Random r)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Estimates parameters of the hypergeometric distribution using maximum likelihood.
        /// </summary>
        public void MaximumLikelihoodEstimate(IEnumerable<int> source)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Estimates parameters of the hypergeometric distribution using Bayesian methods.
        /// </summary>
        public void BayesianEstimate(IEnumerable<int> source, params dynamic[] priorParameters)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEquatable<HypergeometricDistribution> implementation

        public bool Equals(HypergeometricDistribution other)
        {
            return _p.Equals(_p) & _m.Equals(other._m) & _n.Equals(other._n);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return P.GetHashCode() ^ M.GetHashCode() ^ N.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is HypergeometricDistribution))
                throw new InvalidCastException("The 'other' argument is not a HypergeometricDistribution object.");
            
            return Equals(other as HypergeometricDistribution);
        }

        public override string ToString()
        {
            return String.Format("Hypergeometric(N = {0}, m = {1}, n = {2})", _p, _m, _n);
        }

        #endregion
    }
}
